/*      $Id: mod_dir_list.c 1119 2010-12-12 21:29:18Z phrakt $    */
/*
 * Copyright (c) 2007 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <errno.h>
#include <dirent.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <http/msg.h>
#include <http/http.h>
#include <http/entity.h>

#include <canopy/buf.h>
#include <canopy/log.h>
#include <canopy/mib.h>
#include <canopy/sys.h>

#include <httpd/httpd.h>



static char        mod_dir_list_cssuri[CANOPY_PATH_MAX] = "";



static char        mod_dir_list_header[1024] = "";
static char        mod_dir_list_footer[1024] = "";

/*
 * The value of this string determines the output that will be generated by
 * the module.
 *
 * The following characters are recognized:
 *
 *	a	- last access time
 *	c	- creation time
 *	g	- group ownership
 *	M	- file access mode
 *	m	- last modification time
 *	n	- filename
 *	o	- user ownership
 *	s	- file size
 *
 */
static char        mod_dir_list_output[16] = "nMas";

static struct list_field {
	char       f_char;
	char       f_fldname[16];
	char       f_flddesc[32];
} mod_dir_list_fields[] = {
	{ 'a',  "atime",    "Last Access Time"       },
	{ 'c',  "ctime",    "Creation Time"          },
	{ 'g',  "group",    "Group"                  },
	{ 'M',  "mode",     "Mode"                   },
	{ 'm',  "mtime",    "Last Modification Time" },
	{ 'n',  "filename", "Filename"               },
	{ 'o',  "owner",    "Owner"                  },
	{ 's',  "size",     "Size"                   },
};


#define FIELD_TAB_SIZE	(sizeof(mod_dir_list_fields) / sizeof(mod_dir_list_fields[0]))



int   mod_dir_list_init     (void);
void  mod_dir_list_cleanup  (void);
int   mod_dir_list_handler  (struct httpd_hdlr_args *);


static int   mod_dir_list_setupenv     (http_msg_t *);
static void  mod_dir_list_format_mode  (mode_t, char *, size_t);
static void  mod_dir_list_format_time  (time_t, char *, size_t);


/*
 * mod_dir_list_init()
 *
 * Setup the CGI environment.
 */
int
mod_dir_list_init(void)
{
	if (canopy_addhdlr(HTTPD_PHASE_RESPONSE, HTTPD_HDLR_HEAD,
	    "mod_dir_list_handler", mod_dir_list_handler, NULL) == -1) {
		return (-1);
	}

	if ((cnp_mib_mknode(httpd_mib, "canopy.mod.mod_dir_list.css_uri",
	    CNP_MIB_TYPE_STRING, CNP_MIB_MODE_RW, 0) == -1) ||
	    (cnp_mib_bind(httpd_mib, "canopy.mod.mod_dir_list.css_uri",
	    mod_dir_list_cssuri, sizeof(mod_dir_list_cssuri)) == -1)) {
		cnp_log(httpd_logchan, CNP_LOG_ERR,
		    "failed to register MIB variable");
		return (-1);
	}

	if ((cnp_mib_mknode(httpd_mib, "canopy.mod.mod_dir_list.header",
	    CNP_MIB_TYPE_STRING, CNP_MIB_MODE_RW, 0) == -1) ||
	    (cnp_mib_bind(httpd_mib, "canopy.mod.mod_dir_list.header",
	    mod_dir_list_cssuri, sizeof(mod_dir_list_cssuri)) == -1)) {
		cnp_log(httpd_logchan, CNP_LOG_ERR,
		    "failed to register MIB variable");
		return (-1);
	}

	return (0);
}

/*
 * mod_dir_list_cleanup()
 *
 */
void
mod_dir_list_cleanup(void)
{

}

/*
 * mod_dir_list_handler()
 *
 * Main handler for CGI support
 */
int
mod_dir_list_handler(struct httpd_hdlr_args *args)
{
	unsigned int i;
	char pathbuf[CANOPY_PATH_MAX], fldbuf[128], *fldname, *fmtp;
	cnp_buf_t *out_buf;
	struct dirent *ent;
	struct stat fst;
	struct tm tm;
	struct passwd *pwd;
	struct group *grp;
	DIR *dir;
	http_msg_t *req = args->ha_req;
	http_msg_t *resp = args->ha_resp;

	/* we only generate index listings for directories */
	if (resp->msg_entity->ent_type != HTTP_ENT_DIR)
		return (HTTPD_HDLR_OK);

	out_buf = cnp_buf_alloc(512, CNP_BUF_AUTOEXT);
	if (out_buf == NULL) {
		cnp_log(httpd_logchan, CNP_LOG_ERR,
		    "failed to allocate response output buffer");
		return (HTTPD_HDLR_ERR);
	}

	cnp_buf_fappend(out_buf, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD "
	    "HTML 4.01 Transitional//EN\">\n<html>\n<head>\n"
	    "<title>Directory Index of %s</title>\n",
	    http_uri_getpath(req->msg_req.req_url));

	if (mod_dir_list_cssuri[0] != '\0') {
		cnp_buf_fappend(out_buf,
		    "<link href=\"%s\" rel=\"stylesheet\" type=\"text/css\"",
		    mod_dir_list_cssuri);
	}

	cnp_buf_fappend(out_buf, "</head>\n<body>\n");

	cnp_log(httpd_logchan, CNP_LOG_DEBUG,
	    "opening directory %s for listing", resp->msg_entity->ent_path);

	if (mod_dir_list_header[0] != '\0')
		cnp_buf_fappend(out_buf, "%s", mod_dir_list_header);

	cnp_buf_fappend(out_buf, "<table class=\"dirlisting\">\n<tr>\n");

	/* create the table headers */
	for (fmtp = mod_dir_list_output; *fmtp != '\0'; fmtp++) {

		for (i = 0; i < FIELD_TAB_SIZE; i++)
			if (mod_dir_list_fields[i].f_char == *fmtp)
				break;

		if (i == FIELD_TAB_SIZE) {
			cnp_log(httpd_logchan, CNP_LOG_WARN,
			    "unsupported output character `%c' in output "
			    "format description", *fmtp);
			continue;
		}

		cnp_buf_fappend(out_buf,
		    "\t<th><span class=\"colhdr\">%s</span></th>\n",
		    mod_dir_list_fields[i].f_flddesc);
	}
	cnp_buf_fappend(out_buf, "</tr>\n");


	dir = opendir(resp->msg_entity->ent_path);
	if (dir == NULL) {
		cnp_log(httpd_logchan, CNP_LOG_ERRNO,
		    "failed to open directory %s for listing",
		    resp->msg_entity->ent_path);
		return (HTTPD_HDLR_ERR);
	}

	while ((ent = readdir(dir)) != NULL) {
		/* skip the self-reference */
		if ((ent->d_name[0] == '.') && (ent->d_name[1] == '\0'))
			continue;

		snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
		    resp->msg_entity->ent_path, ent->d_name);

		if (stat(pathbuf, &fst) == -1) {
			cnp_log(httpd_logchan, CNP_LOG_ERRNO,
			    "failed to stat %s", pathbuf);
			continue;
		}

		cnp_buf_fappend(out_buf, "<tr>\n");

		for (i = 0; i < strlen(mod_dir_list_output); i++) {
			switch (mod_dir_list_output[i]) {
			case 'a':
				fldname = "atime";
				mod_dir_list_format_time(fst.st_atime, fldbuf,
				    sizeof(fldbuf));
				break;
			case 'c':
				fldname = "ctime";
				mod_dir_list_format_time(fst.st_ctime, fldbuf,
				    sizeof(fldbuf));
				break;
			case 'g':
				fldname = "group";
				grp = getgrgid(fst.st_gid);
				if (grp == NULL)
					snprintf(fldbuf, sizeof(fldbuf),
					    "%d", fst.st_gid);
				else
					strlcpy(fldbuf, grp->gr_name,
					    sizeof(fldbuf));
				break;
			case 'M':
				fldname = "mode";
				mod_dir_list_format_mode(fst.st_mode, fldbuf,
				    sizeof(fldbuf));
				break;
			case 'm':
				fldname = "mtime";
				mod_dir_list_format_time(fst.st_mtime, fldbuf,
				    sizeof(fldbuf));
				break;
			case 'n':
				fldname = "filename";
				snprintf(fldbuf, sizeof(fldbuf),
				    "<a href=\"%s/%s\">%s</a>",
				    http_uri_getpath(req->msg_req.req_url),
				    ent->d_name, ent->d_name);
				break;
			case 'o':
				fldname = "owner";
				pwd = getpwuid(fst.st_uid);
				if (pwd == NULL)
					snprintf(fldbuf, sizeof(fldbuf),
					    "%d", fst.st_uid);
				else
					strlcpy(fldbuf, pwd->pw_name,
					    sizeof(fldbuf));
				break;

			case 's':
				fldname = "size";
				snprintf(fldbuf, sizeof(fldbuf), "%llu",
				    fst.st_size);
			default:
				break;
			}

			cnp_buf_fappend(out_buf,
			    "\t<td><span class=\"%s\">%s</span></td>\n",
			    fldname, fldbuf);
		}


		cnp_buf_fappend(out_buf, "</tr>\n");
	}

	closedir(dir);

	cnp_buf_fappend(out_buf, "</table>\n");

	if (mod_dir_list_footer[0] != '\0')
		cnp_buf_fappend(out_buf, "%s", mod_dir_list_footer);

	cnp_buf_fappend(out_buf, "</body>\n</html>\n");

	resp->msg_resp.resp_code = HTTP_STAT_OK;
	resp->msg_entity->ent_type = HTTP_ENT_DYNAMIC;
	resp->msg_entity->ent_flags |= HTTP_ENTITY_VOLATILE;
	resp->msg_entity->ent_media = strdup("text/html");
	resp->msg_entity->ent_encoding = strdup("UTF-8");

	resp->msg_entity->ent_size = cnp_buf_len(out_buf);
	resp->msg_entity->ent_body = cnp_buf_release(out_buf);

	return (HTTPD_HDLR_OK);
}

/*
 * mod_dir_list_format_mode()
 *
 * Format a file's mode information in a human-readable string.
 */
static void
mod_dir_list_format_mode(mode_t mode, char *buf, size_t len)
{
	strlcpy(buf, "----------", len);

	if (S_ISDIR(mode))
		buf[0] = 'd';
	else if (S_ISCHR(mode))
		buf[0] = 'c';
	else if (S_ISBLK(mode))
		buf[0] = 'b';
	else if (S_ISLNK(mode))
		buf[0] = 'l';
	else if (S_ISSOCK(mode))
		buf[0] = 's';

	if (mode & S_IRUSR)
		buf[1] = 'r';
	if (mode & S_IWUSR)
		buf[2] = 'w';
	if (mode & S_IXUSR)
		buf[3] = 'x';

	if (mode & S_IRGRP)
		buf[4] = 'r';
	if (mode & S_IWGRP)
		buf[5] = 'w';
	if (mode & S_IXGRP)
		buf[6] = 'x';

	if (mode & S_IROTH)
		buf[7] = 'r';
	if (mode & S_IWOTH)
		buf[8] = 'w';
	if (mode & S_IXOTH)
		buf[9] = 'x';
}

/*
 * mod_dir_list_format_time()
 *
 * Format a file's timestamp information in a human-readable string.
 */
static void
mod_dir_list_format_time(time_t timestamp, char *buf, size_t len)
{
	struct tm tm;

	if (localtime_r(&timestamp, &tm) == NULL) {
		cnp_log(httpd_logchan, CNP_LOG_ERRNO, "localtime failed");
		return;
	}

	snprintf(buf, len, "%04d-%02d-%02d %02d:%02d", tm.tm_year + 1900,
	    tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min);
}
